import { Matrix3 } from "./Matrix3.js";
import { Vector2 } from "./Vector2.js";

class Object2D {
    constructor() {
        this.name = '';
        this.type = 'Object2D';

        this.parent = null;
        this.children = [];
        this.parent = null
        this.attributes = {}
        const position = new Vector2();
        const velocity = new Vector2();
        const force = new Vector2();
        this.matrixWorld = new Matrix3();
        this.id = this.guid()

        this.parameters = {
            close: true,
            stroke: true,
            fill: true,
            lineWidth: 1,
            strokeStyle: 'hsl(60, 100%, 90%)',
            fillStyle: 'hsl(220, 100%, 50%)'
        }

        // const rotation = new Euler();
        // const quaternion = new Quaternion();
        const scale = new Vector2(1, 1);

        Object.defineProperties(this, {
            position: {
                configurable: true,
                enumerable: true,
                value: position
            },
            velocity: {
                configurable: true,
                enumerable: true,
                value: velocity
            },
            force: {
                configurable: true,
                enumerable: true,
                value: force
            },
            /*
            rotation: {
              configurable: true,
              enumerable: true,
              value: rotation
            },
            quaternion: {
              configurable: true,
              enumerable: true,
              value: quaternion
            },
            */
            scale: {
                configurable: true,
                enumerable: true,
                value: scale
            },
        });
    }

    isObject2D() {
        return true
    }

    add(object) {
        if (arguments.length > 1) {
            for (let i = 0; i < arguments.length; i++) {
                this.add(arguments[i]);
            }
            return this;
        }
        if (object === this) {
            console.error('Object2D.add: object can\'t be added as a child of itself.', object);
            return this;
        }
        if (object && object.isObject2D) {
            if (object.parent !== null) {
                object.parent.remove(object);
            }
            object.parent = this;
            this.children.push(object);
        } else {
            console.error('Object2D.add: object not an instance of Object2D.', object);
        }
        return this;
    }

    remove(object) {
        if (arguments.length > 1) {
            for (let i = 0; i < arguments.length; i++) {
                this.remove(arguments[i]);
            }
            return this;
        }

        const index = this.children.indexOf(object);
        if (index !== - 1) {
            object.parent = null;
            this.children.splice(index, 1);
        }
        return this;
    }

    localToWorld(vector) {
        return vector.applyMatrix3(this.matrixWorld);
    }

    worldToLocal(vector) {

        return vector.applyMatrix3(new Matrix3().copy(this.matrixWorld).invert());

    }

    removeFromParent() {
        const parent = this.parent;
        if (parent !== null) {
            parent.remove(this);
        }

        return this;
    }

    clear() {
        for (let i = 0; i < this.children.length; i++) {
            const object = this.children[i];
            object.parent = null;
        }
        this.children.length = 0;

        return this;
    }

    getObjectById(id) {
        return this.getObjectByProperty('id', id);
    }

    getObjectByName(name) {
        return this.getObjectByProperty('name', name);
    }

    getObjectByProperty(name, value) {
        if (this[name] === value) return this;
        for (let i = 0, l = this.children.length; i < l; i++) {
            const child = this.children[i];
            const object = child.getObjectByProperty(name, value);
            if (object !== undefined) {
                return object;
            }
        }

        return undefined;
    }

    getAttribute(name) {
        return this.attributes[name];
    }

    setAttribute(name, attribute) {
        this.attributes[name] = attribute;
        return this;
    }

    deleteAttribute(name) {
        delete this.attributes[name];
        return this;
    }

    hasAttribute(name) {
        return this.attributes[name] !== undefined;
    }

    guid() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

    pointerTo() {
        if (this.pointer) {
            const targetX = this.pointer.position.x + this.pointerDiff.x;
            const targetY = this.pointer.position.y + this.pointerDiff.y;
            const strength = 0.0159;
            this.velocity.x += (targetX - this.position.x) * strength;
            this.velocity.y += (targetY - this.position.y) * strength;
            this.velocity.mul(0.99);
            // console.log(this.pointer.x)
            // console.log(this.velocity)
        }
    }

    getNormals() {
        const { position } = this.attributes
        let normals = []
        if (position.length < 2) return normals
        for (let i = 0, l = position.length - 1; i < l; ++i) {
            const j = i + 1
            normals.push(Vector2.sub(position[j], position[i]).rotateAround(Math.PI / 2))
        }
        return normals
    }

    getPositions() {
        const postion = this.getAttribute('position')
        const { velocity } = this
        let vertices = []
        if (postion && postion.length > 0) {
            postion.forEach(pos => {
                const diff = velocity.clone().normalize().dot(pos) * .9
                if (diff >= 0) {
                    vertices.push(Vector2.sub(pos, diff))
                } else {
                    vertices.push(Vector2.add(pos, diff))
                }
            })
        }
        return vertices
    }

}

class Scene extends Object2D {
    constructor() {
        super()
        this.type = 'Scene'
        this.backgroud = ''
        this.isScene = true
    }
}

class Rect extends Object2D {
    constructor(width = 1, height = 1, widthSegments = 1, heightSegments = 1, parameters={}) {
        super()
        this.type = 'Rect'

        this.width = width
        this.height = height
        this.widthSegments = widthSegments
        this.heightSegments = heightSegments

        this.setAttribute('position', this.getPoints())

        Object.assign(this.parameters, parameters)
        this.setAttribute('close', this.parameters.close)
        this.setAttribute('fill', this.parameters.fill)
        this.setAttribute('stroke', this.parameters.stroke)
        this.setAttribute('strokeStyle', this.parameters.strokeStyle)
        this.setAttribute('fillStyle', this.parameters.fillStyle)

        // console.log(this.attributes)
    }

    getPoints() {
        let vertices = []
        const { width, height, widthSegments, heightSegments } = this
        const segment_width = width / widthSegments;
        const segment_height = height / heightSegments;

        const width_half = width / 2;
        const height_half = height / 2;

        for (let ix = 0; ix < widthSegments; ix++) {
            const x = ix * segment_width - width_half
            vertices.push(new Vector2(x, height_half))
        }
        for (let iy = 0; iy < heightSegments; iy++) {
            const y = iy * segment_height - height_half;
            vertices.push(new Vector2(width_half, -y))
        }
        for (let ix = 0; ix < widthSegments; ix++) {
            const x = ix * segment_width - width_half
            vertices.push(new Vector2(-x, -height_half))
        }

        for (let iy = 0; iy < heightSegments; iy++) {
            const y = iy * segment_height - height_half;
            vertices.push(new Vector2(-width_half, y))
        }

        return vertices
    }
}


class Circle extends Object2D {
    constructor(x = 0, y = 0, radius = 10, segments = 8, thetaStart = 0, thetaLength = Math.PI * 2, parameters = { close: true, stroke: true }) {
        super()
        this.type = 'Circle'
        this.position.x = x
        this.position.y = y

        this.radius = radius
        this.segments = segments
        this.thetaStart = thetaStart
        this.thetaLength = thetaLength

        this.setAttribute('position', this.getPoints())

        Object.assign(this.parameters, parameters)
        this.setAttribute('close', this.parameters.close)
        this.setAttribute('fill', this.parameters.fill)
        this.setAttribute('stroke', this.parameters.stroke)
        this.setAttribute('lineWidth', this.parameters.lineWidth)
        this.setAttribute('strokeStyle', this.parameters.strokeStyle)
        this.setAttribute('fillStyle', this.parameters.fillStyle)

        this.setAttribute('normals', this.getNormals())
        // console.log(this.attributes)
    }

    setRadiusChange(offset) {
        this.radius += offset
        this.setAttribute('position', this.getPoints())
    }

    getPoints() {
        let vertices = []
        const { segments, radius, thetaStart, thetaLength } = this
        for (let s = segments; s >= 0; --s) {
            const segment = thetaStart + s / segments * thetaLength;
            // vertex
            const vertex = new Vector2();
            vertex.x = radius * Math.cos(segment);
            vertex.y = radius * Math.sin(segment);

            vertices.push(vertex);
        }
        return vertices
    }
}

class Text extends Object2D {
    constructor(x = 0, y = 0, text='', parameters = { stroke: false }) {
        super()
        this.type = 'Text'
        this.backgroud = ''
        this.isText = true
        this.position.x = x
        this.position.y = y
        this.text = text

        Object.assign(this.parameters, parameters)
        this.setAttribute('font', "15px Georgia")
        this.setAttribute('fill', this.parameters.fill)
        this.setAttribute('stroke', this.parameters.stroke)
        this.setAttribute('strokeStyle', this.parameters.strokeStyle)
        this.setAttribute('fillStyle', this.parameters.fillStyle)
    }
    draw(ctx) {
        ctx.font = this.getAttribute('font')
        if (this.getAttribute('stroke')) {
            ctx.strokeStyle = this.parameters.strokeStyle
            ctx.strokeText(this.text, this.position.x, this.position.y)
        }
        if (this.getAttribute('fill')) {
            ctx.fillStyle = this.parameters.fillStyle
            ctx.fillText(this.text, this.position.x, this.position.y)
        }
    }
}
class Point extends Object2D {
    constructor() {
        super()
        // console.log(this.position)
    }
}


export { Object2D, Scene, Point, Rect, Circle, Text }